package com.ian.config;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.logout.HeaderWriterLogoutHandler;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter;
import org.springframework.security.web.header.writers.DelegatingRequestMatcherHeaderWriter;
import org.springframework.security.web.header.writers.StaticHeadersWriter;
import org.springframework.security.web.header.writers.XContentTypeOptionsHeaderWriter;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.regex.Pattern;

import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy.*;
import static org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter.Directive.*;

/**
 * Security Config
 * @author Witt
 * @version 1.0.0
 * @date 2022/5/9
 */
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(requests -> requests
                        .antMatchers(
//                                "/",
//                                "/*.html",
//                                "/**/*.html",
                                "/**/*.css",
                                "/**/*.js",
                                "/js/**",
                                "/css/**",
                                "/img/**",
                                "/test").permitAll()
                        .anyRequest().authenticated())
                // formLogin()配置是必须的，哪怕是使用默认配置。否则，当访问 /login 时，系统报404错误，说明系统不会自动渲染或绑定/login
                .formLogin(formLogin -> formLogin.permitAll())
                // If a client makes a request using HTTP rather than HTTPS, Spring Security can be configured to redirect to HTTPS
                //   但是如果应用不支持https，就会导致不停地重定向，没法加载正确的页面
//                .requiresChannel(channel -> channel.anyRequest().requiresSecure())
                // Spring Security provides support for Strict Transport Security and enables it by default.
                // Spring Security integrates with proxy servers.
        ;
    }

    @Bean
    StrictHttpFirewall httpFirewall() {
        // By default the StrictHttpFirewall is used
        StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();
        // if you wish to leverage Spring MVC’s Matrix Variables, configure the item below:
        strictHttpFirewall.setAllowSemicolon(true);
        /*
        eg2. Allow Only GET & POST
        The StrictHttpFirewall provides an allowed list of valid HTTP methods that are allowed to protect against
        Cross Site Tracing (XST) and HTTP Verb Tampering. The default valid methods are "DELETE", "GET", "HEAD",
        "OPTIONS", "PATCH", "POST", and "PUT". If your application needs to modify the valid methods,
        you can configure a custom StrictHttpFirewall bean. For example, the following will only allow HTTP "GET" and "POST" methods:
         */
        strictHttpFirewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
        /*
        If you must allow any HTTP method (not recommended), you can use StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true).
        This will disable validation of the HTTP method entirely.
         */
        strictHttpFirewall.setUnsafeAllowAnyHttpMethod(true);
        /*
        eg3. Allow Any Header Name, Header Value, and Parameter Name
        StrictHttpFirewall also checks header names and values and parameter names.
        It requires that each character have a defined code point and not be a control character.
        The default is to reject header names, header values and parameter names that contain ISO control characters and characters that are not defined.
            to switch off this check, you can wire your StrictHttpFirewall with Predicates that always return true, like so:
         */
        strictHttpFirewall.setAllowedHeaderNames(header -> true);
        strictHttpFirewall.setAllowedHeaderValues(header -> true);
        strictHttpFirewall.setAllowedParameterNames(parameter -> true);

        /*
        eg4. Allow Certain User Agents:
        For example, iPhone Xʀ uses a User-Agent that includes a character not in the ISO-8859-1 charset.
        Due to this fact, some application servers will parse this value into two separate characters, the latter being an undefined character.
            You can address this with the setAllowedHeaderValues method, as you can see below to Allow Certain User Agents:
         */
        Pattern allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*");
        Pattern userAgent = Pattern.compile(".*ʀ.*");
        strictHttpFirewall.setAllowedHeaderValues(header -> allowed.matcher(header).matches() || userAgent.matcher(header).matches());
        /*
         eg5. Parse Headers As UTF-8:
         In the case of header values, you may instead consider parsing them as UTF-8 at verification time like so:
         */
        strictHttpFirewall.setAllowedHeaderValues(header -> {
            String parsed = null;
            try {
                parsed = new String(header.getBytes(StandardCharsets.ISO_8859_1), "UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return allowed.matcher(parsed).matches();
        });
        return strictHttpFirewall;
    }

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        UserDetails user = User.withUsername("user").password("123").roles("user").passwordEncoder(pw -> "{bcrypt}" + new BCryptPasswordEncoder().encode(pw)).build();
        // UserDetails user = User.withUsername("user").password("{bcrypt}$2a$10$lmURdwCsjA9jFc5NgzX1Keq5O91WEuOOgEtMQ246X.G/cu.HSqMTG").roles("user").build();
        UserDetails admin = User.withUsername("admin").password("123").roles("admin").passwordEncoder(pw -> "{bcrypt}" + new BCryptPasswordEncoder().encode(pw)).build();
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(user);
        manager.createUser(admin);
        return manager;
    }
}
